﻿using Microsoft.AspNetCore.Http;
using PmSoft.Core.FileStorage;

namespace PmSoft.Web.Abstractions.Services;

/// <summary>
/// 文件上传服务，封装普通上传和分片上传功能
/// </summary>
public class DefaultFileUploadService : IFileUploadService
{
	private readonly IFileStorageProvider _storageProvider;

	/// <summary>
	/// 构造函数，通过依赖注入获取文件存储提供商
	/// </summary>
	public DefaultFileUploadService(IFileStorageProvider storageProvider)
	{
		_storageProvider = storageProvider;
	}

	#region 普通文件上传

	/// <summary>
	/// 上传单个文件（异步）
	/// </summary>
	/// <param name="file">上传的文件</param>
	/// <param name="bucketName">存储桶名称</param>
	/// <param name="fileName">文件名称</param>
	/// <param name="path">存储路径（可选）</param>
	/// <returns>上传成功的文件名</returns>
	public async Task<string> UploadFileAsync(IFormFile file, string bucketName, string fileName, string? path = null)
	{
		ValidateFile(file);
		using var stream = new MemoryStream();
		await file.CopyToAsync(stream);

		var args = new SaveFileArgs
		{
			BucketName = bucketName,
			Path = path,
			ObjectName = fileName,
			Data = stream.ToArray()
		};

		await _storageProvider.SaveFileAsync(args);
		return file.FileName;
	}

	/// <summary>
	/// 上传文件字节数组（异步）
	/// </summary>
	/// <param name="data">文件字节数组</param>
	/// <param name="fileName">文件名</param>
	/// <param name="bucketName">存储桶名称</param>
	/// <param name="path">存储路径（可选）</param>
	/// <returns>上传成功的文件名</returns>
	public async Task<string> UploadFileAsync(byte[] data, string fileName, string bucketName, string? path = null)
	{
		if (data == null || data.Length == 0)
			throw new ArgumentException("文件数据无效。");
		if (string.IsNullOrEmpty(fileName))
			throw new ArgumentException("文件名未提供。");

		var args = new SaveFileArgs
		{
			BucketName = bucketName,
			Path = path,
			ObjectName = fileName,
			Data = data
		};

		await _storageProvider.SaveFileAsync(args);
		return fileName;
	}

	/// <summary>
	/// 上传单个文件（同步）
	/// </summary>
	/// <param name="file">上传的文件</param>
	/// <param name="bucketName">存储桶名称</param>
	/// <param name="fileName">文件名称</param>
	/// <param name="path">存储路径（可选）</param>
	/// <returns>上传成功的文件名</returns>
	public string UploadFile(IFormFile file, string bucketName, string fileName, string? path = null)
	{
		return UploadFileAsync(file, bucketName, fileName, path).GetAwaiter().GetResult();
	}

	#endregion

	#region 分片文件上传

	/// <summary>
	/// 上传文件分片（异步）
	/// </summary>
	/// <param name="file">分片文件</param>
	/// <param name="fileName">完整文件名</param>
	/// <param name="chunkNumber">分片编号（从 1 开始）</param>
	/// <param name="totalChunks">总分片数</param>
	/// <param name="bucketName">存储桶名称</param>
	/// <param name="tempPath">临时存储路径（可选）</param>
	/// <param name="finalPath">最终存储路径（可选）</param>
	/// <returns>分片上传是否完成合并</returns>
	public async Task<(bool IsCompleted, long TotalSize)> UploadChunkAsync(
		IFormFile file,
		string fileName,
		int chunkNumber,
		int totalChunks,
		string bucketName,
		string tempPath = "chunks/temp",
		string finalPath = "files")
	{
		ValidateChunkParams(file, fileName, chunkNumber, totalChunks);
		using var stream = new MemoryStream();
		await file.CopyToAsync(stream);

		var tempObjectName = $"{fileName}.part{chunkNumber}";
		var saveArgs = new SaveFileArgs
		{
			BucketName = bucketName,
			Path = tempPath,
			ObjectName = tempObjectName,
			Data = stream.ToArray()
		};

		await _storageProvider.SaveFileAsync(saveArgs);

		if (chunkNumber == totalChunks)
		{
			var totalSize = await MergeChunksAsync(fileName, totalChunks, bucketName, tempPath, finalPath);
			return (true, totalSize); // 表示分片上传完成并已合并
		}
		return (false, 0); // 表示分片上传未完成
	}

	/// <summary>
	/// 上传分片字节数组（异步）
	/// </summary>
	/// <param name="data">分片字节数组</param>
	/// <param name="fileName">完整文件名</param>
	/// <param name="chunkNumber">分片编号（从 1 开始）</param>
	/// <param name="totalChunks">总分片数</param>
	/// <param name="bucketName">存储桶名称</param>
	/// <param name="tempPath">临时存储路径（可选）</param>
	/// <param name="finalPath">最终存储路径（可选）</param>
	/// <returns>分片上传是否完成合并</returns>
	public async Task<(bool IsCompleted, long TotalSize)> UploadChunkAsync(
		byte[] data,
		string fileName,
		int chunkNumber,
		int totalChunks,
		string bucketName,
		string tempPath = "chunks/temp",
		string finalPath = "files")
	{
		if (data == null || data.Length == 0)
			throw new ArgumentException("分片数据无效。");
		ValidateChunkParams(null, fileName, chunkNumber, totalChunks);

		var tempObjectName = $"{fileName}.part{chunkNumber}";
		var saveArgs = new SaveFileArgs
		{
			BucketName = bucketName,
			Path = tempPath,
			ObjectName = tempObjectName,
			Data = data
		};

		await _storageProvider.SaveFileAsync(saveArgs);

		if (chunkNumber == totalChunks)
		{
			var totalSize = await MergeChunksAsync(fileName, totalChunks, bucketName, tempPath, finalPath);
			return (true, totalSize);
		}
		return (false, 0);
	}

	/// <summary>
	/// 上传文件分片（同步）
	/// </summary>
	/// <param name="file">分片文件</param>
	/// <param name="fileName">完整文件名</param>
	/// <param name="chunkNumber">分片编号（从 1 开始）</param>
	/// <param name="totalChunks">总分片数</param>
	/// <param name="bucketName">存储桶名称</param>
	/// <param name="tempPath">临时存储路径（可选）</param>
	/// <param name="finalPath">最终存储路径（可选）</param>
	/// <returns>分片上传是否完成合并</returns>
	public (bool IsCompleted, long TotalSize) UploadChunk(
		IFormFile file,
		string fileName,
		int chunkNumber,
		int totalChunks,
		string bucketName,
		string tempPath = "chunks/temp",
		string finalPath = "files")
	{
		return UploadChunkAsync(file, fileName, chunkNumber, totalChunks, bucketName, tempPath, finalPath)
			.GetAwaiter().GetResult();
	}

	/// <summary>
	/// 移动文件
	/// </summary>
	/// <param name="sourceBucketName">源桶名称</param>
	/// <param name="sourceObjectName">源对象名称</param>
	/// <param name="destBucketName">目标桶名称</param>
	/// <param name="destObjectName">目标对象名称</param>
	/// <returns>表示异步操作的任务</returns>
	public async Task MoveFileAsync(string sourceBucketName, string sourceObjectName, string destBucketName, string destObjectName)
	{
		var moveFileArgs = new MoveFileArgs
		{
			SourceBucketName = sourceBucketName,
			SourceObjectName = sourceObjectName,
			DestBucketName = destBucketName,
			DestObjectName = destObjectName
		};
		await _storageProvider.MoveFileAsync(moveFileArgs);
	}

	#endregion

	#region 私有方法

	/// <summary>
	/// 验证文件是否有效
	/// </summary>
	private void ValidateFile(IFormFile file)
	{
		if (file == null || file.Length == 0)
			throw new ArgumentException("未提供有效的文件。");
	}

	/// <summary>
	/// 验证分片参数
	/// </summary>
	private void ValidateChunkParams(IFormFile? file, string fileName, int chunkNumber, int totalChunks)
	{
		if (file != null) ValidateFile(file);
		if (string.IsNullOrEmpty(fileName))
			throw new ArgumentException("文件名未提供。");
		if (chunkNumber < 1 || totalChunks < 1 || chunkNumber > totalChunks)
			throw new ArgumentException("分片参数无效。");
	}

	/// <summary>
	/// 合并分片文件
	/// </summary>
	/// <param name="fileName"></param>
	/// <param name="totalChunks"></param>
	/// <param name="bucketName"></param>
	/// <param name="tempPath"></param>
	/// <param name="finalPath"></param>
	/// <returns>合并后总大小</returns>
	private async Task<long> MergeChunksAsync(string fileName, int totalChunks, string bucketName, string tempPath, string finalPath)
	{
		using var mergedStream = new MemoryStream();
		long totalSize = 0;
		for (int i = 1; i <= totalChunks; i++)
		{
			var tempObjectName = $"{fileName}.part{i}";
			var getArgs = new GetFileArgs
			{
				BucketName = bucketName,
				Path = tempPath,
				ObjectName = tempObjectName
			};

			var fileData = await _storageProvider.GetFileAsync(getArgs);
			if (fileData != null)
			{
				totalSize += fileData.Data.Length;
				await mergedStream.WriteAsync(fileData.Data, 0, fileData.Data.Length);
			}
		}

		var finalSaveArgs = new SaveFileArgs
		{
			BucketName = bucketName,
			Path = finalPath,
			ObjectName = fileName,
			Data = mergedStream.ToArray()
		};
		await _storageProvider.SaveFileAsync(finalSaveArgs);

		// 删除临时分片
		for (int i = 1; i <= totalChunks; i++)
		{
			var tempObjectName = $"{fileName}.part{i}";
			var deleteArgs = new DeleteFileArgs
			{
				BucketName = bucketName,
				Path = tempPath,
				ObjectName = tempObjectName
			};
			await _storageProvider.DeleteFileAsync(deleteArgs);
		}
		return totalSize;
	}

	#endregion
}
